/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.bodies;

import org.apache.commons.math.geometry.Vector3D;
import org.apache.commons.math.util.MathUtils;

/**
 * Point location on the surface of a spherical body.
 * 
 * Instances of this class are immutable.
 * 
 * @see BodyShape
 * 
 * @author Luc Maisonobe
 */
public class GeodeticPoint
    extends Object
    implements java.io.Serializable
{

    /**
     * Latitude of the point (rad).
     */
    public final double latitude;
    /**
     * Longitude of the point (rad).
     */
    public final double longitude;
    /**
     * Altitude of the point (m).
     */
    public final double altitude;

    private transient Vector3D zenith;
    private transient Vector3D nadir;
    private transient Vector3D north;
    private transient Vector3D south;
    private transient Vector3D east;
    private transient Vector3D west;



    public GeodeticPoint(double latitude, double longitude, double altitude){
        super();
        this.latitude  = MathUtils.normalizeAngle(latitude,  0);
        this.longitude = MathUtils.normalizeAngle(longitude, 0);
        this.altitude  = altitude;
    }


    public final double getLatitude() {
        return this.latitude;
    }
    public final double getLongitude() {
        return this.longitude;
    }
    public final double getAltitude() {
        return altitude;
    }
    /** 
     * Get the direction above the point, expressed in parent shape
     * frame.
     * 
     * The zenith direction is defined as the normal to local
     * horizontal plane.
     * 
     * @return Unit vector in the zenith direction
     */
    public final Vector3D getZenith() {
        if (this.zenith == null) {
            double cosLat = Math.cos(this.latitude);
            double sinLat = Math.sin(this.latitude);
            double cosLon = Math.cos(this.longitude);
            double sinLon = Math.sin(this.longitude);
            this.zenith = new Vector3D((cosLon * cosLat),
                                       (sinLon * cosLat), 
                                       sinLat);
        }
        return this.zenith;
    }
    /**
     * Get the direction below the point, expressed in parent shape
     * frame.
     * 
     * The nadir direction is the opposite of zenith direction.</p>
     * 
     * @return Unit vector in the nadir direction
     */
    public final Vector3D getNadir() {
        if (this.nadir == null)
            this.nadir = this.getZenith().negate();

        return this.nadir;
    }
    /** 
     * Get the direction to the north of point, expressed in parent
     * shape frame.
     * 
     * The north direction is defined in the horizontal plane (normal
     * to zenith direction) and following the local meridian.
     * 
     * @return Unit vector in the north direction
     */
    public final Vector3D getNorth() {
        if (this.north == null) {
            double cosLat = Math.cos(this.latitude);
            double sinLat = Math.sin(this.latitude);
            double cosLon = Math.cos(this.longitude);
            double sinLon = Math.sin(this.longitude);
            this.north = new Vector3D((-cosLon * sinLat), 
                                      (-sinLon * sinLat), 
                                      cosLat);
        }
        return this.north;
    }
    /** 
     * Get the direction to the south of point, expressed in parent shape frame.
     * 
     * The south direction is the opposite of north direction.
     * 
     * @return unit vector in the south direction
     */
    public final Vector3D getSouth() {
        if (this.south == null)
            this.south = this.getNorth().negate();

        return this.south;
    }
    /** 
     * Get the direction to the east of point, expressed in parent
     * shape frame.
     * 
     * The east direction is defined in the horizontal plane in order
     * to complete direct triangle (east, north, zenith).
     * 
     * @return Unit vector in the east direction
     */
    public final Vector3D getEast() {
        if (this.east == null)
            this.east = new Vector3D(-Math.sin(this.longitude), 
                                      Math.cos(this.longitude), 
                                      0);

        return this.east;
    }
    /** 
     * Get the direction to the west of point, expressed in parent shape frame.
     * 
     * The west direction is the opposite of east direction.
     * 
     * @return unit vector in the west direction
     */
    public final Vector3D getWest() {
        if (this.west == null)
            this.west = this.getEast().negate();

        return this.west;
    }
}
